import copy
import math
import time
import lib_MJ as MJ  # 使用的一些库函数
import logging
import datetime
import itertools

# global variable
TIME_START = time.time()
w_type = 0  # lib_MJ的权重选择
ROUND = 0  # 轮数
t3Set = MJ.get_t3info()
t2Set, t2Efc, efc_t2index = MJ.get_t2info()
REMAIN_NUM = 112  # 剩余牌数
KING = None  # 百搭牌

T_SELFMO = [0] * 34  # 自摸概率表，牌存在于牌墙中的概率表
LEFT_NUM = [0] * 34  # 未出现的牌的数量表
RT1 = [[0] * 34, [0] * 34]  # 其他玩家的状态表[table1,table2] ，不需要的牌table1与需要的牌table2，
RT2 = [[0] * 34, [0] * 34]  # table1:计算吃碰的概率，table2：计算危险度
RT3 = [[0] * 34, [0] * 34]

t1tot3_dict = MJ.t1tot3_info()  # t1转化为t3
t2tot3_dict = MJ.t2tot3_info()  # t2转化为t3

class Node_PH:
    def __init__(self, take=None, AAA=[], ABC=[], jiang=[], T2=[], T1=[], raw=[],taking_set=[], taking_set_w=[], baida_num=0):
        self.take = take
        self.AAA = AAA
        self.ABC = ABC
        self.jiang = jiang
        self.T2 = T2
        self.T1 = T1
        self.raw = raw
        self.baida_num = baida_num
        self.children = []
        self.taking_set = taking_set
        self.taking_set_w = taking_set_w

    def add_child(self, child):
        self.children.append(child)

    def node_info(self):
        print ('AAA=',self.AAA, 'ABC=',self.ABC, 'jiang=',self.jiang, "T1=", self.T1, "T2=", self.T2, 'raw=',self.raw,
               'taking_set=',self.taking_set, 'baida_num=',self.baida_num, 'fei_baida=')

class SearchTree_PH():
    """
    平胡搜索模块
    """
    def __init__(self, hand, suits, combination_sets, baida_card=None, hua_num = 0):
        """
        类变量初始化
        :param hand: 手牌
        :param suits: 副露
        :param combination_sets: 拆分组合集合
        :param baida_card: 百搭牌
        """
        self.hand = hand
        self.suits = suits
        self.combination_sets = combination_sets
        self.xts = combination_sets[0][-2]
        self.tree_dict = []
        self.baida_card = baida_card
        if baida_card != None:
            self.baida_num = hand.count(baida_card)
        else:
            self.baida_num = 0
        self.discard_score = {}  # 出牌集合的评估值集合
        self.discard_state = {}  # 出牌集合的状态集合  {card：[[出card后希望获取的组合],[获取每个组合的评估值]]}
        self.node_num = 0  # 统计节点数目（观测值）
        self.chang_num = 0  # 统计状态不同但分数相同的节点 （观测值）
        self.hua_num = hua_num

    def expand_node(self, node):
        """
        节点扩展.首先扩展将牌，再扩展t3:先扩展t2->t3,再t1->t3,使用itertools.combinations生成待扩展集合可以有效减少重复计算量
        :param node:
        :return: None
        """
        # node.node_info()
        #先定将
        if node.jiang==[]:
            has_jiang = False
            # if node.baida_num >= 2:  # 百搭还原
            #     has_jiang = True  # 有百搭还原时不再搜索无将情况
            #     child = Node_PH(take=-1, AAA=node.AAA, ABC=node.ABC, jiang=[self.baida_card, self.baida_card],
            #                     T2=node.T2,
            #                     T1=node.T1,
            #                     taking_set=node.taking_set, taking_set_w=node.taking_set_w,
            #                     baida_num=node.baida_num - 2)
            #     node.add_child(child=child)
            #     self.expand_node(child)

            if node.baida_num > 0:  # 百搭吊
                has_jiang = True  # 百搭吊不再搜索无将
                taking_set = copy.copy(node.taking_set)
                taking_set.append(0)  # 填充0
                taking_set_w = copy.copy(node.taking_set_w)
                taking_set_w.append(1)
                child = Node_PH(take=0, AAA=node.AAA, ABC=node.ABC, jiang=[0, 0], T2=node.T2,
                                T1=node.T1,
                                taking_set=taking_set, taking_set_w=taking_set_w, baida_num=node.baida_num - 1)
                node.add_child(child=child)
                self.expand_node(child)

            if node.baida_num <= 1:
                for t2 in node.T2:  # 有将
                    T2 = MJ.deepcopy(node.T2)
                    # 从t2中找到对子作为将牌
                    if t2[0] == t2[1]:
                        has_jiang = True  # 有将不再搜索无将
                        T2.remove(t2)
                        child = Node_PH(take=-1, AAA=node.AAA, ABC=node.ABC, jiang=t2, T2=T2,
                                        T1=node.T1,
                                        taking_set=node.taking_set, taking_set_w=node.taking_set_w,
                                        baida_num=node.baida_num)  
                        node.add_child(child=child)
                        self.expand_node(node=child)

            # 无将的情况
            if not has_jiang:  # todo 这里可以考虑有将时也扩展
                jiangs = copy.copy(node.T1)
                # 没有T1,则选择一个t2来扩展
                # todo 可以在有T1时也扩展该部分
                if jiangs == []:
                    for t2 in node.T2:
                        jiangs = t2
                        T2 = MJ.deepcopy(node.T2)
                        T2.remove(t2)
                        for t1 in jiangs:
                            taking_set = copy.copy(node.taking_set)
                            taking_set.append(t1)
                            taking_set_w = copy.copy(node.taking_set_w)
                            taking_set_w.append(1)
                            T1 = copy.copy(jiangs)
                            T1.remove(t1)
                            child = Node_PH(take=t1, AAA=node.AAA, ABC=node.ABC, jiang=[t1, t1], T2=T2,
                                            T1=T1,
                                            taking_set=taking_set, taking_set_w=taking_set_w, baida_num=node.baida_num)
                            node.add_child(child=child)
                            self.expand_node(node=child)
                # 从T1中选择一张作为将
                else:
                    for t1 in jiangs:
                        if t1 == -1:  # op填充的-1不作扩展
                            continue
                        taking_set = copy.copy(node.taking_set)
                        taking_set.append(t1)
                        taking_set_w = copy.copy(node.taking_set_w)
                        taking_set_w.append(1)
                        T1 = copy.copy(jiangs)
                        T1.remove(t1)
                        child = Node_PH(take=t1, AAA=node.AAA, ABC=node.ABC, jiang=[t1, t1], T2=node.T2,
                                        T1=T1,
                                        taking_set=taking_set, taking_set_w=taking_set_w, baida_num=node.baida_num)
                        node.add_child(child=child)
                        self.expand_node(node=child)

        # 胡牌判断,存在将牌的前提下去判断是否胡牌
        elif len(node.AAA) + len(node.ABC) == 4:
            # if node.baida_num > 0:
            #     node.fei_baida += node.baida_num  # 多余的百搭牌没使用，作为弃牌飞掉
            #     node.baida_num = 0
            return

        # T3扩展
        else:
            # 当待扩展集合不为空时，使用该集合进行扩展
            if node.raw != []:
                tn = node.raw[-1]
                raw = copy.copy(node.raw)  # 深度搜索后面的节点会改变raw，回退可能导致前面的节点raw不正确，这里需要copy
                raw.pop()
                if type(tn) == list:  # 使用t2扩展t3
                    t2 = tn
                    for item in t2tot3_dict[str(t2)]:    #t2tot3_dict: {'[1, 1]': [[[1, 1], [1, 1, 1], [], 1, 6]]
                        if item[1][0] == item[1][1]:   #AA
                            AAA = MJ.deepcopy(node.AAA)
                            AAA.append(item[1])
                            ABC = node.ABC
                        else:#
                            AAA = node.AAA
                            ABC = MJ.deepcopy(node.ABC)
                            ABC.append(item[1])
                        # if node.baida_num > 0 and item[-2] == self.baida_card:  # 百搭还原,需要的牌和百搭牌一样
                        #     child = Node_PH(take=-1, AAA=AAA, ABC=ABC, jiang=node.jiang, T2=node.T2,
                        #                       T1=node.T1, raw=raw, taking_set=node.taking_set, taking_set_w=node.taking_set_w,
                        #                       baida_num=node.baida_num-1)
                        #     node.add_child(child=child)
                        #     self.expand_node(node=child)

                        if node.baida_num > 0 and (0 in node.jiang):  # 百搭牌补一张
                            child = Node_PH(take=0, AAA=AAA, ABC=ABC, jiang=node.jiang, T2=node.T2,
                                              T1=node.T1, raw=raw, taking_set=node.taking_set, taking_set_w=node.taking_set_w,
                                              baida_num=node.baida_num-1)
                            node.add_child(child=child)
                            self.expand_node(node=child)
                        else:  # 正常打法
                            taking_set = copy.copy(node.taking_set)
                            taking_set_w = copy.copy(node.taking_set_w)
                            taking_set.append(item[-2])
                            taking_set_w.append(item[-1])
                            child = Node_PH(take=item[-2], AAA=AAA, ABC=ABC, jiang=node.jiang, T2=node.T2,
                                              T1=node.T1, raw=raw, taking_set=taking_set, taking_set_w=taking_set_w,
                                              baida_num=node.baida_num)
                            node.add_child(child=child)
                            self.expand_node(node=child)
                # t1扩展为t3
                elif type(tn) == int:
                    t1 = tn
                    for item in t1tot3_dict[str(t1)]:   #t1tot3_dict: {'1': [[[1, 1, 1], [1, 1], [1, 6]], [[1, 2, 3], [2, 3], [1, 2]]], '2': [[[1, 2, 3], [1, 3], [1, 2]], [[2, 2, 2], [2, 2], [1, 6]], [[2, 3, 4], [3, 4],[1, 2]]]
                        flag2 = False
                        # if node.baida_num > 0:  # 用于处理百搭还原
                        #     for card in item[1]:
                        #         if card == self.baida_card:
                        #             flag2 = True  # 百搭还原标识
                        #             raw_copy = copy.copy(raw)
                        #             raw_copy.append(sorted([card, t1]))
                        #             child = Node_PH(take=-1, AAA=node.AAA, ABC=node.ABC, jiang=node.jiang, T2=node.T2, T1=node.T1, raw=raw_copy,
                        #                             taking_set=node.taking_set, taking_set_w=node.taking_set_w,
                        #                             baida_num=node.baida_num - 1)
                        #             node.add_child(child=child)
                        #             self.expand_node(node=child)
                        # if flag2:  # 上述百搭还原后不再继续扩展
                        #     continue

                        if item[0][0] == item[0][1]:
                            AAA = MJ.deepcopy(node.AAA)
                            AAA.append(item[0])
                            ABC = node.ABC
                        else:
                            AAA = node.AAA
                            ABC = MJ.deepcopy(node.ABC)
                            ABC.append(item[0])

                        if node.baida_num >= 2:  # 百搭牌有2张以上，直接补2张，即使其中有一张被作为百搭还原也不影响
                            child = Node_PH(take=[0, 0], AAA=AAA, ABC=ABC, jiang=node.jiang, T2=node.T2, T1=node.T1,raw=raw,
                                            taking_set=node.taking_set, taking_set_w=node.taking_set_w,
                                            baida_num=node.baida_num - 2)
                            node.add_child(child=child)
                            self.expand_node(node=child)

                        elif node.baida_num == 0:  # 百搭数量为0 的处理
                            take = item[1]
                            take_w = item[-1]

                            taking_set = copy.copy(node.taking_set)
                            taking_set.extend(take)
                            taking_set_w = copy.copy(node.taking_set_w)
                            taking_set_w.extend(take_w)
                            child = Node_PH(take=take, AAA=AAA, ABC=ABC, jiang=node.jiang, T2=node.T2, T1=node.T1,raw=raw,
                                            taking_set=taking_set, taking_set_w=taking_set_w,
                                            baida_num=node.baida_num)
                            node.add_child(child=child)
                            self.expand_node(node=child)

                        elif node.baida_num == 1:  # baida_num=1 ,补一张牌
                            # 用1张百搭牌
                            for i in range(len(item[1])):
                                card = item[1][i]
                                take = [0, card]

                                taking_set = copy.copy(node.taking_set)
                                taking_set.append(card)
                                taking_set_w = copy.copy(node.taking_set_w)
                                taking_set_w.append(1)

                                child = Node_PH(take=take, AAA=AAA, ABC=ABC, jiang=node.jiang, T2=node.T2, T1=node.T1,raw=raw,
                                                taking_set=taking_set, taking_set_w=taking_set_w,
                                                baida_num=node.baida_num - 1)
                                node.add_child(child=child)
                                self.expand_node(node=child)
                        else:
                            pass
                            # logger.error("node.baida_num==%s", (node.baida_num))
                else:
                    pass
                    # logger.error("tn Error")
            else:
                if node.T2 != []:  # 1、先扩展T2为T3
                    t2_sets = node.T2
                    T2 = copy.copy(node.T2)
                    # 生成待扩展集合
                    for t2_set in itertools.combinations(t2_sets, min(4 - len(node.AAA) - len(node.ABC),len(t2_sets))):
                        node.T2 = copy.copy(T2)
                        node.raw = list(t2_set)
                        for t2 in node.raw:
                            node.T2.remove(t2)
                        self.expand_node(node=node)
                #  生成T1扩展T3集合
                elif node.T1 != []:
                    t1_sets = copy.copy(node.T1)
                    #这里移除了填充的-1，不作扩展
                    if -1 in t1_sets:
                        t1_sets.remove(-1)
                    T1 = copy.copy(node.T1)
                    for t1_set in itertools.combinations(t1_sets, min(4 - len(node.AAA) - len(node.ABC),len(t1_sets))):
                        node.T1 = copy.copy(T1)
                        node.raw = list(t1_set)
                        for t1 in node.raw:
                            node.T1.remove(t1)
                        self.expand_node(node=node)

    def generate_tree(self):
        """
        生成树
        :return: None
        """
        kz = []
        sz = []
        # 将副露加入到节点的AAA和ABC状态中
        for t3 in self.suits:
            if t3[0] == t3[1]:
                kz.append(t3)
            else:
                sz.append(t3)
        # 使用拆分组合生成树
        for cs in self.combination_sets:
            # print(cs)
            root = Node_PH(take=None, AAA=cs[0] + kz, ABC=cs[1] + sz, jiang=[], T2=cs[2] + cs[3], T1=cs[-1],
                           taking_set=[], taking_set_w=[], baida_num=self.baida_num)
            # 每一棵树都存储到树集合中
            self.tree_dict.append(root)
            self.expand_node(node=root)

    def cal_score(self, node):
        """
        节点评估值计算模块
        :param node:
        :return: float 评估值
        """
        value = 1
        if node.taking_set_w != []:
            # 上饶麻将中胡牌需要自摸，获取权重为1。这里将具有最小获取权重的牌的权重置为1.是一种权重最大化的处理.todo 可以尝试其他的处理
            node.taking_set_w[node.taking_set_w.index(min(node.taking_set_w))]=1
            for i in range(len(node.taking_set)):
                card = node.taking_set[i]
                if card == 0:  # 百搭吊的任意牌,获取概率为1
                    taking_rate = 1.0
                else:  # 其他牌的获取概率计算
                    taking_rate = T_SELFMO[MJ.convert_hex2index(card)]
                value *= taking_rate*node.taking_set_w[i]  # todo 需要结合其他玩家打出这张牌的概率来计算，将获取权重具体化

        # 摸牌概率修正，当一张牌被重复获取时，T_selfmo修改为当前数量占未出现牌数量的比例
        taking_set = list(set(node.taking_set))
        taking_set_num = [node.taking_set.count(i) for i in taking_set]
        for i in range(len(taking_set_num)):
            n = taking_set_num[i]
            j = 0
            while n > 1:
                j += 1
                index = MJ.convert_hex2index(taking_set[i])
                if LEFT_NUM[index] >= n:
                    value *= float(LEFT_NUM[index]-j) / LEFT_NUM[index]
                else:  # 摸牌数超过了剩余数，直接舍弃
                    value = 0
                    return value
                n -= 1

        # fan计算
        # print(node.AAA, node.ABC, node.jiang, self.suits)
        fan = Fan_PH(kz=node.AAA, sz=node.ABC, jiang=node.jiang, baida_card = self.baida_card, hua_num=self.hua_num, suits = self.suits).fanDetect()
        score = Score_PH(kz=node.AAA, sz=node.ABC, jiang=node.jiang, baida_card=self.baida_card, hua_num=self.hua_num,
                     suits=self.suits).ScoreDetect()
        res = fan * score * value
        #print('PH',fan,value)
        return res

    def calculate_path_expectation(self, node):
        """
        计算整条路径的上的评估值，并将其赋予为所有出牌的评估值
        :param node:
        :return:
        """
        # 深度搜索。搜索胡牌的叶子节点
        if len(node.AAA) + len(node.ABC) == 4 and node.jiang != []:  #胡牌
            # Node_PH.node_info(node)
            self.node_num += 1
            discard_set = []  # 出牌集合
            # 计算没有使用的飞百搭，将其加入到出牌中
            # for i in range(node.fei_baida - self.fei_baida):
            #     discard_set.append(self.baida_card)
            #     break
            # 将没有使用的T2加入到出牌中
            for t2 in node.T2:
                discard_set.extend(t2)
            discard_set.extend(node.T1)
            taking_set_sorted = sorted(node.taking_set)
            taking_set_lable = str(taking_set_sorted)  #转化为str可以加快查找
            if discard_set != []:
                score = self.cal_score(node=node)  # 放到外面统一计算，减少耗时
            else:
                return
            # todo 这种按摸牌的评估方式是否唯一准确
            for card in list(set(discard_set)):
                if card not in self.discard_state.keys():  #没有就新建一个空的
                    self.discard_state[card] = [[], []]
                if taking_set_lable not in self.discard_state[card][0]:
                    self.discard_state[card][0].append(taking_set_lable)
                    self.discard_state[card][-1].append(score)
                else:  #score取最大的
                    index = self.discard_state[card][0].index(taking_set_lable)
                    if score > self.discard_state[card][-1][index]:
                        self.chang_num += 1
                        self.discard_state[card][-1][index] = score

        elif node.children != []:
            for child in node.children:
                self.calculate_path_expectation(node=child)

    def get_discard_score(self):
        """
        总接口。获取出牌的评估值
        :return: dict. 出牌的评估值集合
        """
        # t1 = time.time()
        self.generate_tree()
        # t2 = time.time()
        for root in self.tree_dict:
            self.calculate_path_expectation(root)
        # t3 = time.time()
        # print ("tree time:",t2 - t1, "value time:",t3 - t2)
        state_num = 0
        for discard in self.discard_state.keys():
            if discard not in self.discard_score.keys():
                self.discard_score[discard] = 0
            self.discard_score[discard] = sum(self.discard_state[discard][-1])
            state_num += len(self.discard_state[discard][-1])

        # print('平胡的信息----------------')
        # print ("discard_state", self.discard_state)
        # print ("discard_score", self.discard_score)
        #print ("leaf node ", self.node_num)
        #print ("state_num", state_num)
        #print ("chang_num", self.chang_num)
        #print('++++++++++++++++++++++++')
        return self.discard_score


'''
番数计算类
'''
class Fan_PH():
    def __init__(self, kz, sz, jiang, fei_baida=0, baida_card = 0, hua_num = 0, suits = []):
        """
        初始化类变量
        :param kz: 刻子
        :param sz: 顺子
        :param jiang: 将
        :param node: 待检测的结点
        :param fei_baida: 飞百搭数
        """
        self.kz = kz
        self.sz = sz
        self.jiang = jiang
        self.fei_baida = fei_baida
        self.baida_card = baida_card
        self.hua_num = hua_num
        self.suits = suits
        self.mul = 2

    def shoubayi(self):
        """
        手把一判断
        手把一：其他牌已经碰下或者杠下，独听最后一张牌
        """
        return (len(self.suits) == 4)

    def wubaida(self):
        """
        无百搭检测
        """
        for aaa in self.kz:
            if aaa[0] == self.baida_card:
                return True

        for abc in self.sz:
            for card in abc:
                if card == self.baida_card:
                    return True
        if self.jiang[0] == self.baida_card:
            return True

        return False

    # 碰碰胡
    def pengPengHu(self):
        """
        碰碰胡检测
        是否刻子树数达到４个
        :return: bool
        """
        if len(self.kz) == 4:
            return True
        else:
            return False

    # 清一色 x2
    def qingYiSe(self):
        """
        清一色检测
        手牌为同一花色
        :return: bool
        """
        # todo 百搭吊无法检测清一色，因为将牌无法确定
        w = 0
        ti = 0
        to = 0
        # print self.kz + self.sz+ self.jiang
        for t in self.kz + self.sz + [self.jiang]:
            card = t[0]
            if card != 0:
                if card & 0xf0 == 0x00:
                    w = 1
                elif card & 0xf0 == 0x10:
                    ti = 1
                elif card & 0xf0 == 0x20:
                    to = 1
                else:
                    return False

        if w + ti + to <= 1:
            return True
        else:
            return False

    def menqing(self):
        return self.suits == 0 and self.hua_num == 0


    def fanDetect(self):
        """
        番数计算
        7.牌型倍率：
            a.杠开：2倍
            b.七对：2倍
            c.手把一：2倍
            d.清一色：4倍
            e.天胡：4倍

        :return: int 番数
        """
        # 基础分判定
        fan = 1
        if self.pengPengHu():
            fan *= 2
        if self.qingYiSe():
            fan *= 4
        if self.shoubayi():
            fan *= 2
        if self.menqing():
            fan *= 2
        return fan

class Score_PH():
    def __init__(self, kz, sz, jiang, fei_baida=0, baida_card = 0, hua_num = 0, suits = []):
        """
        初始化类变量
        :param kz: 刻子
        :param sz: 顺子
        :param jiang: 将
        :param node: 待检测的结点
        :param fei_baida: 飞百搭数
        """
        self.kz = kz
        self.sz = sz
        self.jiang = jiang
        self.fei_baida = fei_baida
        self.baida_card = baida_card
        self.hua_num = hua_num
        self.suits = suits
        self.mul = 2

    def hypergeometric_expectation(n, m, x):
        """
        计算不放回抽取情况下满足要求的牌的期望值

        参数:
        n (int): 总牌数（需 > 0）
        m (int): 满足要求的牌数（0 ≤ m ≤ n）
        x (int): 抽取数量（0 ≤ x ≤ n）

        返回:
        float: 期望值

        异常:
        ValueError: 输入参数不合法时抛出
        """
        # 参数校验
        if n <= 0:
            raise ValueError("n必须为正整数")
        if not (0 <= m <= n):
            raise ValueError("m需在0到n之间")
        if not (0 <= x <= n):
            raise ValueError("x需在0到n之间")

        return (x * m) / n

    def _wubaida(self):
        """
        无百搭检测
        """
        for aaa in self.kz:
            if aaa[0] == self.baida_card:
                return True

        for abc in self.sz:
            for card in abc:
                if card == self.baida_card:
                    return True
        if self.jiang[0] == self.baida_card:
            return True

        return False

    def _fangyang(self):
        """
        放羊检测
        """
        return self.jiang == [0,0]

    def niaofen(self, x):
        """
        鸟分计算 抓x鸟
        """
        global LEFT_NUM
        if(sum(LEFT_NUM) == 0) :
            return 0

        # print(LEFT_NUM)
        # print(sum(LEFT_NUM))
        m = LEFT_NUM[0] + LEFT_NUM[4] + LEFT_NUM[8] + LEFT_NUM[9] + LEFT_NUM[13] + LEFT_NUM[17] \
              + LEFT_NUM[18] + LEFT_NUM[22] + LEFT_NUM[26] + LEFT_NUM[27]

        return m * x / sum(LEFT_NUM)

    def huafen(self):
        '''花分计suan
        '''
        if self.hua_num == 4:
            return 6
        return self.hua_num

    def gangfen(self):
        """
        杠分计算 包括百搭皮
        """
        gangfen = 0

        if [self.baida_card, self.baida_card, self.baida_card] in self.kz:
            gangfen += 2
        for suit in self.suits:
            if len(suit) == 4:
                gangfen += 1
            if suit[0] == suit[1] and suit[0] == self.baida_card:
                gangfen += 1

        return gangfen

    def paixingfen(self):
        """
        牌型分检测 （无百搭 + 放羊）
        """
        score = 0
        if self._wubaida():
            score += 1
        if self._fangyang():
            score += 1

        return score

    def ScoreDetect(self):
        """
        分数计算
        1.积分=(底分+鸟分+花分+杠分+放羊+牌型分)*牌型倍率；
            a.自摸三家给；
            b.抢杠胡和放胡(一家给)；
        2.底分：2分。（创建房间选多少底分就是多少）
        3.鸟分：胡牌后从牌尾翻出对应的的抓鸟张数，翻出的牌中带1,5,9，红中/百搭的即为中鸟，每张鸟额外加1分
        4.花分：每个红中+1花，4张红中算6花。
        5.杠分：
            a.明杠：1分（碰百搭皮算明杠）
            b.暗杠：2分（三张百搭皮在手算暗杠）
        6.牌型分：
            a.放羊：1分。
            b.无百搭：1分

        :return: int 分数
        """
        # 底分
        score = 2
        score += self.niaofen(0)
        score += self.huafen()
        score += self.gangfen()
        score += self.paixingfen()

        return score

'''
平胡类，相关处理方法
分为手牌拆分模块sys_info，评估cost,出牌决策，吃碰杠决策等部分
'''
class PingHu:
    '''
    平胡类模块
    '''

    def __init__(self, cards, suits, baidaCard=None,fei_baida=0,padding=[]):
        """
        类变量初始化
        :param cards: 手牌　
        :param suits:副露
        :param leftNum:剩余牌数量列表
        :param discards:弃牌
        :param discards_real:实际弃牌
        :param discardsOp:场面副露
        :param round:轮数
        :param remainNum:牌墙剩余牌数量
        :param seat_id:座位号
        :param baidaCard:百搭牌
        :param fei_baida:飞百搭数
        :param op_card:动作操作牌
        """
        cards.sort()
        self.cards = cards
        self.suits = suits
        self.baidaCard = baidaCard
        self.fei_baida=fei_baida
        self.padding=padding
        self.baidaNum=cards.count(baidaCard)

    @staticmethod
    def split_type_s(cards=[]):
        """
        功能：手牌花色分离，将手牌分离成万条筒字各色后输出
        :param cards: 手牌　[]
        :return: 万,条,筒,字　[],[],[],[]
        """
        cards_wan = []
        cards_tiao = []
        cards_tong = []
        cards_zi = []
        for card in cards:
            if card & 0xF0 == 0x00:
                cards_wan.append(card)
            elif card & 0xF0 == 0x10:
                cards_tiao.append(card)
            elif card & 0xF0 == 0x20:
                cards_tong.append(card)
            elif card & 0xF0 == 0x30:
                cards_zi.append(card)
        return cards_wan, cards_tiao, cards_tong, cards_zi

    @staticmethod
    def get_effective_cards(dz_set=[]):
        """
        获取有效牌
        :param dz_set: 搭子集合 list [[]]
        :return: 有效牌 list []
        """
        effective_cards = []
        for dz in dz_set:
            if len(dz) == 1:
                effective_cards.append(dz[0])
            elif dz[1] == dz[0]:
                effective_cards.append(dz[0])
            elif dz[1] == dz[0] + 1:
                if int(dz[0]) & 0x0F == 1:
                    effective_cards.append(dz[0] + 2)
                elif int(dz[0]) & 0x0F == 8:
                    effective_cards.append((dz[0] - 1))
                else:
                    effective_cards.append(dz[0] - 1)
                    effective_cards.append(dz[0] + 2)
            elif dz[1] == dz[0] + 2:
                effective_cards.append(dz[0] + 1)
        effective_cards = set(effective_cards)  # set 和list的区别？
        return list(effective_cards)

    # 判断３２Ｎ是否存在于ｃａｒｄｓ中
    @staticmethod
    def in_cards(t32=[], cards=[]):
        """
        判断３２Ｎ是否存在于ｃａｒｄｓ中
        :param t32: ３Ｎ或2N组合牌
        :param cards: 本次判断的手牌
        :return: bool
        """
        for card in t32:
            if card not in cards:
                return False
        return True

    @staticmethod
    def get_32N(cards=[]):
        """
        功能：计算所有存在的手牌的３Ｎ与２Ｎ的集合，例如[3,4,5]　，将得到[[3,4],[3,5],[4,5],[3,4,5]]
        思路：为减少计算量，对长度在12张以上的单花色的手牌，当存在顺子时，不再计算搭子
        :param cards: 手牌　[]
        :return: 3N与2N的集合　[[]]
        """
        cards.sort()
        kz = []
        sz = []
        aa = []
        ab = []
        ac = []
        lastCard = 0
        # 对长度在12张以上的单花色的手牌，当存在顺子时，不再计算搭子
        if True:
            for card in cards:
                if card == lastCard:
                    continue
                else:
                    lastCard = card
                if cards.count(card) >= 3:
                    kz.append([card, card, card])
                if cards.count(card) >= 2:
                    aa.append([card, card])
                if card + 1 in cards and card + 2 in cards:
                    sz.append([card, card + 1, card + 2])
                else:
                    if card + 1 in cards:
                        ab.append([card, card + 1])
                    if card + 2 in cards:
                        ac.append([card, card + 2])
        return kz + sz + aa + ab + ac

    def extract_32N(self, cards=[], t32_branch=[], t32_set=[]):
        """
        功能：递归计算手牌的所有组合信息，并存储在t32_set，
        思路: 每次递归前检测是否仍然存在３２N的集合,如果没有则返回出本此计算的结果，否则在手牌中抽取该３２N，再次进行递归
        :param cards: 手牌
        :param t32_branch: 本次递归的暂存结果
        :param t32_set: 所有组合信息
        :return: 结果存在t32_set中
        """
        t32N = self.get_32N(cards=cards)
        #print(t32N)

        if len(t32N) == 0:
            t32_set.extend(t32_branch)
            # t32_set.extend([cards])
            t32_set.append(0)
            t32_set.extend([cards])
        else:
            for t32 in t32N:
                if self.in_cards(t32=t32, cards=cards):
                    cards_r = copy.copy(cards)
                    for card in t32:
                        cards_r.remove(card)
                    t32_branch.append(t32)
                    self.extract_32N(cards=cards_r, t32_branch=t32_branch, t32_set=t32_set)
                    if len(t32_branch) >= 1:
                        t32_branch.pop(-1)

    def tree_expand(self, cards):
        """
        功能：对extract_32N计算的结果进行处理同一格式，计算万条筒花色的组合信息
        思路：对t32_set的组合信息进行格式统一，分为[kz,sz,aa,ab,xts,leftCards]保存，并对划分不合理的地方进行过滤，例如将３４５划分为35,4为废牌的情况
        :param cards: cards [] 万条筒其中一种花色手牌
        :return: allDeWeight　[kz,sz,aa,ab,xts,leftCards] 去除不合理划分情况的组合后的组合信息
        """
        all = []
        t32_set = []
        self.extract_32N(cards=cards, t32_branch=[], t32_set=t32_set)
        #print("t32",t32_set)
        kz = []
        sz = []
        t2N = []
        aa = []
        length_t32_set = len(t32_set)
        i = 0
        # for i in range(len(t32_set)):
        while i < length_t32_set:
            t = t32_set[i]
            flag = True  # 本次划分是否合理
            if t != 0:
                if len(t) == 3:
                    if t[0] == t[1]:
                        kz.append(t)
                    else:
                        sz.append(t)  # print (sub)
                elif len(t) == 2:
                    if t[1] == t[0]:
                        aa.append(t)
                    else:
                        t2N.append(t)

            else:
                '修改，使计算时间缩短'
                leftCards = t32_set[i + 1]
                efc_cards = self.get_effective_cards(dz_set=t2N)  # t2N中不包含ａａ
                # 去除划分不合理的情况，例如345　划分为34　或35等，对于333 划分为33　和3的情况，考虑有将牌的情况暂时不做处理
                for card in leftCards:
                    if card in efc_cards:
                        flag = False
                        break

                if flag:
                    all.append([kz, sz, aa, t2N, 0, leftCards])
                kz = []
                sz = []
                aa = []
                t2N = []
                i += 1
            i += 1

        allSort = []  # 给每一个元素排序
        allDeWeight = []  # 排序去重后

        for e in all:
            for f in e:
                if f == 0:  # 0是xts位，int不能排序
                    continue
                else:
                    f.sort()
            allSort.append(e)

        for a in allSort:
            if a not in allDeWeight:
                allDeWeight.append(a)

        allDeWeight = sorted(allDeWeight, key=lambda k: (len(k[0]), len(k[1]), len(k[2])), reverse=True)  # 居然可以这样排序！！
        return allDeWeight

    @staticmethod
    def zi_expand(cards=[]):
        """
        功能：计算字牌组合信息
        思路：字牌组合信息需要单独计算，因为没有字顺子，迭代计算出各张字牌的２Ｎ和３Ｎ的情况，由于某些情况下，可能只会需要ａａ作为将牌的情况，同时需要刻子和ａａ的划分结果
        :param cards: 字牌手牌
        :return: ziBranch　字牌的划分情况　[kz,sz,aa,ab,xts,leftCards]
        """
        cardList = []
        for i in range(7):
            cardList.append([])
        ziCards = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37]
        for card in ziCards:
            index = (card & 0x0f) - 1
            # print(index)

            if cards.count(card) == 4:
                # 此结构为[3N,2N,leftCards]
                cardList[index].append([[[card, card, card]], [], [], [], 0, [card]])
            elif cards.count(card) == 3:
                cardList[index].append([[[card, card, card]], [], [], [], 0, []])
                cardList[index].append([[], [], [[card, card]], [], 0, [card]])
            elif cards.count(card) == 2:

                cardList[index].append([[], [], [[card, card]], [], 0, []])
            elif cards.count(card) == 1:
                cardList[index].append([[], [], [], [], 0, [card]])
            else:
                cardList[index].append([[], [], [], [], 0, []])

        ziBranch = []
        for c1 in cardList[0]:
            for c2 in cardList[1]:
                for c3 in cardList[2]:
                    for c4 in cardList[3]:
                        for c5 in cardList[4]:
                            for c6 in cardList[5]:
                                for c7 in cardList[6]:
                                    branch = []
                                    for n in range(6):
                                        branch.append(c1[n] + c2[n] + c3[n] + c4[n] + c5[n] + c6[n] + c7[n])
                                    ziBranch.append(branch)
        return ziBranch



    def pinghu_CS2(self, cards=[], suits=[], t1=[]):
        """
        功能：综合计算手牌的组合信息
        思路：对手牌进行花色分离后，单独计算出每种花色的组合信息　，再将其综合起来，计算每个组合向听数，最后输出最小向听数及其加一的组合
        :param cards: 手牌
        :param suits: 副露
        :param left_num: 剩余牌
        :param baidaCard: 百搭牌
        :return: 组合信息
        """
        # 去除百搭牌计算信息，后面出牌和动作决策再单独考虑百搭牌信息
        if cards==[]:
            cards = self.cards
            suits=self.suits
        RM_King = copy.copy(cards)
        baidaNum = 0
        if self.baidaCard != None:
            baidaNum = cards.count(self.baidaCard)
            for i in range(baidaNum):
                RM_King.remove(self.baidaCard)

        # 花色分离
        wan, tiao, tong, zi = self.split_type_s(RM_King)
        wan_expd = self.tree_expand(cards=wan)
        tiao_expd = self.tree_expand(cards=tiao)
        tong_expd = self.tree_expand(cards=tong)
        zi_expd = self.zi_expand(cards=zi)

        all = []
        for i in wan_expd:
            for j in tiao_expd:
                for k in tong_expd:
                    for m in zi_expd:
                        branch = []
                        # 将每种花色的4个字段合并成一个字段
                        for n in range(6):
                            branch.append(i[n] + j[n] + k[n] + m[n])

                        branch[-1]+=self.padding+t1
                        all.append(branch)

        # 将获取概率为０的组合直接丢弃到废牌中 todo 由于有百搭，这里也可能会被百搭代替
        # 移到了出牌决策部分处理
        if self.baidaNum <= 1:#这里只考虑出牌、百搭做百搭吊的情况
            for a in all:
                for i in range(len(a[3]) -1, -1, -1):
                    ab = a[3][i]
                    efc = self.get_effective_cards([ab])
                    if sum([LEFT_NUM[MJ.convert_hex2index(e)] for e in efc]) == 0:
                        a[3].remove(ab)
                        a[-1].extend(ab)
                        # logger.info("remove rate 0 ab,%s,%s,%s,a=%s",self.cards,self.suits,self.baidaCard,a)

        # 计算向听数
        # 计算拆分组合的向听数
        all = MJ.cal_xts(all, suits, baidaNum)
        # print('all:',all)

        # 获取向听数最小的all分支
        min_index = 0
        for i in range(len(all)):
            if all[i][4] > all[0][4]:  # xts+１以下的组合
                min_index = i
                break

        if min_index == 0:  # 如果全部都匹配，则min_index没有被赋值，将min_index赋予ａｌｌ长度
            min_index = len(all)

        all = all[:min_index]

        #处理向听数为0时的情况，需要从中依次选择一张牌作为t1
        if all[0][-2] == 0 and all[0][-1] == []:
            all = []
            for card in list(set(cards)):
                cards_ = copy.copy(cards)
                cards_.remove(card)
                all += self.pinghu_CS2(cards=cards_, suits=suits, t1=[card])
        # print(all)
        return all


class Node_Qidui:
    def __init__(self,take=None, AA=[], T1=[], raw=[],taking_set=[], baida_num=0):
        """
        七对节点变量初始化
        :param take: 摸牌
        :param AA: 对子集合
        :param T1: 单张牌集合
        :param raw: 待扩展集合
        :param taking_set: 已摸牌集合
        :param baida_num: 未使用的百搭数量
        """
        self.take = take
        self.AA=AA
        self.T1 = T1
        self.raw = raw
        self.taking_set = taking_set
        self.baida_num = baida_num
        self.children = []

    def add_child(self,child):
        self.children.append(child)

    def node_info(self):
        print ('AA:',self.AA,'T1:',self.T1,'raw:',self.raw,'taking_set:',self.taking_set,'baida_num',self.baida_num)

class Qidui:
    def __init__(self,cards,suits,baida_card,padding=[], hua_num = 0):
        """
        七对类变量初始化
        :param cards: 手牌
        :param suits: 副露
        :param baida_card: 百搭牌
        :param fei_baida: 飞百搭数量
        :param padding: 填充牌，op操作时填充-1 ，一般来说，七对不会有这种操作
        """
        self.cards = cards
        self.suits = suits
        self.baida_card = baida_card
        self.discard_score = {}
        self.baida_num = cards.count(baida_card)
        self.padding=padding
        self.tree_list = []
        self.discard_state = {}
        self.hua_num = hua_num

    def qidui_CS(self):
        """
        计算七对组合的生成
        :return:
        """
        CS = [[],[],14]
        if self.suits!=[]:
            return CS
        cards_rm_baida = copy.copy(self.cards)
        for i in range(self.baida_num):
            cards_rm_baida.remove(self.baida_card)  #去除King的手牌
        for card in list(set(cards_rm_baida)):
            n = cards_rm_baida.count(card)
            if n==1:
                CS[1].append(card)
            elif n==2:
                CS[0].append([card,card])
            elif n==3:
                CS[0].append([card,card])
                CS[1].append(card)
            elif n==4:
                CS[0].append([card,card])
                CS[0].append([card,card])
        baida_num = self.baida_num
        # 这里把百搭用掉
        while baida_num>0:
            if len(CS[0]) + baida_num > 7:
                CS[0].append([self.baida_card,self.baida_card])
                baida_num-=2
            else:
                CS[0].append([0, 0])
                baida_num-=1
        CS[-1]-=len(CS[0])*2+(7-len(CS[0]))
        # CS[-1]+=2  # todo 这里给七对的xt+2，减少后面选择打七对的概率
        if CS[-1]>=4:  # todo  如果对子的数量过少，不建议打七对
            CS[-1]+=3
        if CS[-1]<0:
            CS[-1]=0
        return CS

    def expand_node(self,node):
        """
        节点扩展
        :param node:
        :return:
        """
        # 与平胡类似，先生成待扩展集合，再进行节点扩展
        if len(node.AA)==7:  #胡牌判断
            return
        else:
            if node.raw !=[]:
                # for card in node.raw:
                card = node.raw[-1]
                node.raw.pop()
                AA = copy.copy(node.AA)
                AA.append([card, card])
                taking_set = copy.copy(node.taking_set)
                taking_set.append(card)
                child = Node_Qidui(take=card, AA=AA, T1=node.T1, raw=node.raw,taking_set=taking_set, baida_num=node.baida_num)
                node.add_child(child=child)
                self.expand_node(node=child)
            else:
                if node.T1 != []:
                    t1_sets = copy.copy(node.T1)
                    # if -1 in t1_sets:
                    #     t1_sets.remove(-1)
                    T1 = copy.copy(node.T1)
                    for t1_set in itertools.combinations(t1_sets, min(7 - len(node.AA),len(t1_sets))):
                        node.T1 = copy.copy(T1)
                        node.raw = list(t1_set)
                        for t1 in node.raw:
                            node.T1.remove(t1)
                        self.expand_node(node=node)

    def generate_tree(self):
        """
        生成树
        :return:
        """
        CS = self.qidui_CS()
        # print "qidui CS",CS
        node = Node_Qidui(take=None, AA=CS[0], T1=CS[1], taking_set=[], baida_num=self.baida_num)
        self.tree_list.append(node)
        self.expand_node(node=node)

    def qingYiSe(self, node):
        """
        清一色检测
        手牌为同一花色
        :return: bool
        """
        w = 0
        ti = 0
        to = 0
        # print self.kz + self.sz+ self.jiang
        for t in node.AA:
            card = t[0]
            if card != 0:
                if card & 0xf0 == 0x00:
                    w = 1
                elif card & 0xf0 == 0x10:
                    ti = 1
                elif card & 0xf0 == 0x20:
                    to = 1
                else:
                    return False

        if w + ti + to <= 1:
            return True
        else:
            return False

    def fan(self,node):
        """
        七对番型
        :param node:
        """
        fan = 2

        if self.hua_num == 0:
            fan *= 2
        if self.qingYiSe(node):
            fan *= 4

        return fan

    def score(self, node):
        """
        七对得分 无杠分
        :param node:
        """
        score = 2 # 底分

        # 鸟分
        global LEFT_NUM
        if(sum(LEFT_NUM) == 0) :
            score += 0
        else:
            # print(LEFT_NUM)
            # print(sum(LEFT_NUM))
            m = LEFT_NUM[0] + LEFT_NUM[4] + LEFT_NUM[8] + LEFT_NUM[9] + LEFT_NUM[13] + LEFT_NUM[17] \
                  + LEFT_NUM[18] + LEFT_NUM[22] + LEFT_NUM[26] + LEFT_NUM[27]
            score += m * 0 / sum(LEFT_NUM)

        # 花分
        score += self.hua_num
        if self.hua_num == 4:
            score += 2

        # 牌型分
        wubaida_flag = True
        for aa in node.AA:
            # print()
            if aa == [0,0]:
                fangyang_flag = False

        if wubaida_flag:
            score += 1

        return score


    def evaluate(self,node):
        """
        节点评估值计算
        :param node:
        :return:
        """
        if node.children==[]:
            if len(node.AA)==7:
                #node.node_info()
                taking_set_sorted = sorted(node.taking_set)
                value = 1
                for card in taking_set_sorted:
                    # print "card",card
                    if card == -1:
                        value = 1.0/34
                    else:
                        value *=T_SELFMO[MJ.convert_hex2index(card)]
                fan = self.fan(node=node)
                score = self.score(node = node)
                #print('QD',fan,value)

                score = value * fan * score
                discards=node.T1+self.padding
                for discard in discards:
                    if discard not in self.discard_state.keys():
                        self.discard_state[discard]=[[],[]]
                        self.discard_state[discard][0].append(taking_set_sorted)
                        self.discard_state[discard][-1].append(score)
                    elif taking_set_sorted not in self.discard_state[discard][0]:
                        self.discard_state[discard][0].append(taking_set_sorted)
                        self.discard_state[discard][-1].append(score)
        else:
            for child in node.children:
                self.evaluate(child)

    def get_discard_score(self):
        """
        生成所有合理出牌的评估值
        :return: {card:score}
        """
        # t1 = time.time()
        self.generate_tree()
        # t2 = time.time()
        for tree in self.tree_list:
            self.evaluate(tree)
        # t3=time.time()
        # print ("qidui time",t2-t1,t3-t2)
        for discard in self.discard_state.keys():
            if discard not in self.discard_score:
                self.discard_score[discard] = 0
            self.discard_score[discard] = sum(self.discard_state[discard][-1])
        return self.discard_score


def value_t1(card):
    """
    TODO 用自摸的概率去代表该牌转化成t3的概率？
    计算出牌的危险度评估值，由该牌转化为t3的概率组成
    :param card:
    :return:
    """
    value = 0
    if card!=-1:
        for e in t1tot3_dict[str(card)]:#t1tot3_dict: {'1': [[[1, 1, 1], [1, 1], [1, 6]], [[1, 2, 3], [2, 3], [1, 2]]],
            v = 1
            for i in range(len(e[1])):   #e[1]需要的牌，e[-1]权重
                v *= T_SELFMO[MJ.convert_hex2index(e[1][i])] * e[-1][i]
            value += v
    return value

def get_score_dict(cards,suits,baida_card,padding=[],max_xts=14,hua_num = 0):
    """
    计算各牌型的评估值
    :param cards: 手牌
    :param suits: 副露
    :param baida_card:  百搭牌
    :param fei_baida: 飞百搭
    :param padding: 填充牌。用于计算：op缺一张牌时，填充-1
    :param max_xts: 允许的最大向听数，否则停止计算，用于处理：op中非平胡牌型的吃碰杠处理，例如十三烂牌型吃碰导致需要计算平胡牌型的出牌评估值，从而导致超时
    :return: score_dict,min_xts ，各出牌的评估值与本轮计算的最小向听数（用于op中对比操作前后时）
    """
    # print(cards)

    #寻找向听数在阈值内的牌型
    PH = PingHu(cards=cards, suits=suits, baidaCard=baida_card,padding=padding)
    QD = Qidui(cards=cards,suits=suits,baida_card=baida_card,padding=padding, hua_num= hua_num)
    #组合信息
    CS_PH = PH.pinghu_CS2()
    CS_QD = QD.qidui_CS()
    # print(CS_QD)

    xts_list = [CS_PH[0][-2], CS_QD[-1]]
    min_xts = min(xts_list)
    # op中吃碰后向听数增加的情况，特别是打非平胡的牌型
    if min_xts>max_xts+1:
        return {cards[-1]:0}, min_xts

    type_list = []  # 需搜索的牌型
    for i in range(2):
        if xts_list[i]-1 <= min_xts:
            type_list.append(i)

    score_list = []
    time_start = time.time()
    time_list = []
    for i in type_list:
        if i == 0:
            search_PH = SearchTree_PH(hand=cards, suits=suits, combination_sets=CS_PH, baida_card=baida_card, hua_num=hua_num)
            score_list.append(search_PH.get_discard_score())
        elif i == 1:
            score_list.append(QD.get_discard_score())
        time_list.append(time.time()-time_start-sum(time_list))

    score_dict = {}
    #print("score_list", score_list)
    for score in score_list:
        for key in score.keys():
            if key not in score_dict.keys():
                score_dict[key] = score[key]-float(value_t1(key))/(10**(min_xts+1)/2) # 用来区分相同权重的出牌
            else:
                score_dict[key] += score[key]
    return score_dict,min_xts

def recommend_card(cards=[], suits=[], baida_card=None, discards=[], discards_op=[], remain_num=112,
                   round=0, seat_id=0, hua_num = 0):
    """
    功能：推荐出牌接口
    思路：使用向听数作为牌型选择依据，对最小ｘｔｓ的牌型，再调用相应的牌型类出牌决策
    :param cards: 手牌
    :param suits: 副露
    :param baida_card: 百搭牌
    :param discards: 弃牌
    :param discards_op: 场面副露
    :param remain_num: 剩余牌
    :param round 回合数
    :param seat_id 座位号
    :param hua_num 花牌数
    :return: outCard 推荐出牌
    """

    if 49 in cards: # 花牌直接扔
        return 49

    global T_SELFMO, LEFT_NUM, TIME_START, RT1, RT2, RT3, ROUND, t2tot3_dict, t1tot3_dict

    ROUND = round
    MJ.KING = baida_card
    TIME_START = time.time()
    # 计算获取概率
    LEFT_NUM, _ = MJ.trandfer_discards(discards=discards, discards_op=discards_op, handcards=cards)
    LEFT_NUM[MJ.translate16_33(MJ.pre_baida(baida_card))] -= 1
    REMAIN_NUM = max(1,min(sum(LEFT_NUM), remain_num))  #剩余牌的总数

    T_SELFMO = [float(i) / REMAIN_NUM for i in LEFT_NUM]
    RT1 = []
    RT2 = []
    RT3 = []

    # 计算所有可能出牌的评估值
    score_dict,_= get_score_dict(cards = cards, suits = suits,baida_card = baida_card, hua_num = hua_num)
    # print("score_dict", score_dict)
    if score_dict != {}:
        # score_dict_max = score_dict[max(score_dict, key=lambda x: score_dict[x])]
        recommend_card = max(score_dict, key=lambda x: score_dict[x])
    else: # 手牌可能已经胡了，这里出一张牌，一般不可能发生
        recommend_card = cards[-1]
    end = time.time()

    # if end - TIME_START > 3:  # 超时输出
    #     pass
    return recommend_card

def recommend_op(op_card, cards=[], suits=[], baida_card=None, discards=[], discards_op=[], canchi=False,
                 self_turn=False, isHu=False, round=0, hua_num = 0):

    """
    功能：动作决策接口
    思路：使用向听数作为牌型选择依据，对最小ｘｔｓ的牌型，再调用相应的牌型类动作决策
    :param op_card: 操作牌
    :param cards: 手牌
    :param suits: 副露
    :param baida_card: 百搭牌
    :param discards: 弃牌
    :param discards_op: 场面副露
    :param self_turn: 是否是自己回合
    :param canchi: 吃牌权限
    :param fei_baida: 飞百搭数
    :param isHu: 是否胡牌
    :round 轮数
    :hua_num 花牌数
    :return: [],isHu 动作组合牌，是否胡牌
    """
    # print("================================================")
    # print(cards, baida_card)
    # print(cards, isHu)
    if isHu:
        return [], True

    #print('op_card:',op_card)
    # 更新全局变量
    global T_SELFMO, LEFT_NUM, t2tot3_dict, t1tot3_dict, TIME_START
    MJ.KING = baida_card
    TIME_START = time.time()
    LEFT_NUM, discards_list = MJ.trandfer_discards(discards=discards, discards_op=discards_op, handcards=cards)
    LEFT_NUM[MJ.translate16_33(MJ.pre_baida(baida_card))] -= 1
    REMAIN_NUM = sum(LEFT_NUM)
    T_SELFMO = [float(i) / REMAIN_NUM for i in LEFT_NUM]
    RT1 = []
    RT2 = []
    RT3 = []

    # t1tot3_dict = MJ.t1tot3_info(T_selfmo=T_SELFMO, RT1=[], RT2=[], RT3=[])
    # t2tot3_dict = MJ.t2tot3_info(T_selfmo=T_SELFMO, RT1=[], RT2=[], RT3=[])

    #计算操作前评估值
    cards_pre = copy.copy(cards)
    # cards_pre.append(-1) #加入一张0作为下次摸到的牌，并提升一定的概率a
    score_dict_pre,min_xts_pre = get_score_dict(cards = cards_pre,suits = suits,baida_card =  baida_card, padding=[-1], hua_num = hua_num)
    # xts_pre = min
    if score_dict_pre!={}:
        score_pre = max(score_dict_pre.values())   #选一个最大的score值作为操作前
    else:
        score_pre = 0

    #计算操作后的评估值
    #确定可选动作
    set_cards = list(set(cards))
    if self_turn: #自己回合，暗杠或补杠
        for card in set_cards:
            if cards.count(card)==4:
                return [card,card,card,card],False #暗杠必杠
        for suit in suits:
            if suit.count(suit[0])==3 and suit[0] in cards:
                return suit+[suit[0]],False

    else: #其他玩家回合 #明杠，吃碰
        if cards.count(op_card)==3:  #杠
            return [op_card,op_card,op_card,op_card],False

        op_sets = []#可操作的集合
        #print(canchi)
        if canchi:
            # 计算可吃组合
            if op_card<0x30: #字牌不能吃
                rm_sets = [[op_card-2,op_card-1],[op_card-1,op_card+1],[op_card+1,op_card+2]]#所有可能操作的集合
            else:
                rm_sets = []
            for op_set in rm_sets:
                if op_set[0] in cards and op_set[1] in cards:
                    op_sets.append(op_set)
            # 碰
            if cards.count(op_card) >= 2:
                op_sets.append([op_card,op_card])
        else:
            if cards.count(op_card) >= 2:
                op_sets.append([op_card,op_card])

        #print(op_sets)
        score_set = []
        for op_set in op_sets:
            cards_ = copy.copy(cards)
            cards_.remove(op_set[0])
            cards_.remove(op_set[1])

            suits_ = MJ.deepcopy(suits)
            suits_.append(sorted(op_set+[op_card]))
            score_dict,_= get_score_dict(cards=cards_, suits=suits_, baida_card=baida_card,max_xts=min_xts_pre, hua_num=hua_num)
            # max_discard = max(score_dict, key=lambda x: score_dict[x])
            # print ("score_dict",score_dict)
            if score_dict!={}:
                if score_dict.keys() == op_card:  # 过吃过碰
                    continue
                score = max(score_dict.values())
                score_set.append(score)
        if time.time() - TIME_START > 3:
            pass
            # logger.warning("op time out %s", time.time() - TIME_START)
        # print('op_set:',op_sets)
        # print('score_set:',score_set)
        #print(score_pre)
        if score_set==[]:
            return [], False
        else:
            max_score = max(score_set)
            # print max_score, score_pre
            # print('max_score:', max_score, 'score_pre', score_pre)
            if max_score > score_pre*1.05:
                return sorted(op_sets[score_set.index(max_score)] + [op_card]), False
    return [],False